<?php

/**
 *    缓存服务
 *
 *    为应用提供缓存服务，本包共提供两种缓存服务
 *    <ol>
 *      <li>文件缓存</li>
 *      <li>memcache缓存</li>
 *    </ol>
 *
 *    @author    Garbin
 *    @package    Library
 *    @subpackage Cache
 */

/**
 * 获取缓存服务实例
 *
 * <pre>
 * 使用该方法获取缓存服务实例，可通过修改配置项改变缓存服务类型及相关配置。
 * 相关配置项
 * cache.type       缓存类型，可选值有memcache, filecache
 * cache.servers    当type为memcache时需要指定
 * cache.path       当type为filecache时需要指定，缓存存放路径
 * cache.max_dir    当type为filecache时需要指定，最大子目录数
 * </pre>
 *
 * @author Garbin
 * @return Object
 **/
function __cache()
{
    static $cache_server = null;
    if ($cache_server === null)
    {
        $type = app_config('cache.type');
        switch ($type)
        {
            case 'memcache':
                $cache_server = new MemcachedServer();
                $cache_server->addServers(app_config('cache.servers'));
                break;

            case 'filecache':
            default:
                $cache_server = new FilecacheServer();
                $path = app_config('cache.path');
                $max_dir = app_config('cache.max_dir');
                $path && $cache_server->setPath($path);
                is_int($max_dir) && $cache_server->setMaxdir($max_dir);
                break;
        }
    }

    return $cache_server;
}

/**
 * 缓存服务器连接类接口
 *
 * 定义缓存服务类的标准接口
 */
interface ICacheServer
{
    /**
     * 获取缓存数据
     *
     * 获取指定$key的缓存数据，返回数据或已过期返回false
     *
     * @author    Garbin
     * @param     string $key 唯一标识
     * @return    mixed  返回缓存数据或已过期false
     */
    public function get($key);

    /**
     * 缓存数据
     *
     * 缓存指定数据，使用$key标识该缓存数据，并可指定过期时间，过期时间以秒为单位
     *
     * @author    Garbin
     * @param     string $key   缓存唯一标识
     * @param     mixed  $value 缓存数据
     * @param     int    $ttl   过期时间
     * @return    bool          是否设置成功，成功返回true,反之为false
     */
    public function set($key, $value, $ttl = 0);

    /**
     * 清空缓存
     *
     * 清空所有缓存
     *
     * @author    Garbin
     * @return    bool 成功返回true，失败返回false
     */
    public function clear();

    /**
     * 删除一个缓存
     *
     * 删除指定$key的缓存数据
     *
     * @author    Garbin
     * @param     string $key   缓存标识
     * @return    bool  成功返回true，失败返回false
     */
    public function delete($key);
}

/**
 *    基于文件的缓存服务器
 *
 *    @author    Garbin
 *    @usage    none
 */
class FilecacheServer  implements ICacheServer
{
    /**
     * 默认配置
     */
    protected $_config = array(
        'path' => './cache/',
        'max_dir' => 500,
    );

    /**
     * 设置缓存文件存放目录
     *
     * 该存放目录必须可写，否则缓存无法正常工作，默认值是"./cache"目录
     *
     * @author    Garbin
     * @access public
     * @param string    $path   缓存文件存放目录
     * @return void
     */
    public function setPath($path)
    {
        $this->_config['path'] = $path;
    }


    /**
     * 设置单目录最多文件数
     *
     * 设置该数据可在缓存文件量大时提高系统性能，值越小性能越高，默认500
     *
     * @author    Garbin
     * @access public
     * @param  int $max_dir 最大目录数
     * @return void
     */
    public function setMaxdir($max_dir)
    {
        $this->_config['max_dir'] = $max_dir;
    }

    public function set($key, $value, $ttl = 0)
    {
        if (!$key)
        {
            return false;
        }

        // 值不能为false，因为false在get时会被使用到，如果缓存不存在，会返回false
        if ($value === false)
        {
            $value = 0;
        }

        $cache_file = $this->_getCachePath($key);
        $cache_data = "<?php\r\n/**\r\n *  @Created By Sparrow FilecacheServer\r\n *  @Time:" . date('Y-m-d H:i:s') . "\r\n */";
        $cache_data .= $this->_getExpireCondition(intval($ttl));
        $cache_data .= "\r\nreturn " . var_export($value, true) .  ";\r\n";
        $cache_data .= "\r\n?>";

        return file_put_contents($cache_file, $cache_data, LOCK_EX);
    }

    public function get($key)
    {
        $cache_file = $this->_getCachePath($key);
        if (!is_file($cache_file))
        {
            return false;
        }
        $data = include($cache_file);

        return $data;
    }

    public function clear()
    {
        $dir = dir($this->_config['path']);
        while (false !== ($item = $dir->read()))
        {
            if ($item == '.' || $item == '..' || substr($item, 0, 1) == '.')
            {
                continue;
            }
            $item_path = $this->_config['path'] . '/' . $item;
            if (is_dir($item_path))
            {
                srmdir($item_path);
            }
            else
            {
                @unlink($item_path);
            }
        }

        return true;
    }

    public function delete($key)
    {
        $cache_file = $this->_getCachePath($key);

        return unlink($cache_file);
    }

    /**
     * 获取过期条件语句
     *
     * 该语句将被写入缓存文件，用于判断缓存文件是否过期
     *
     * @author    Garbin
     * @access public
     * @param  int $ttl 过期时间
     * @return string 返回过期条件PHP代码
     */
    protected function _getExpireCondition($ttl = 0)
    {
        if (!$ttl)
        {
            return '';
        }

        return "\r\n\r\n" . 'if(filemtime(__FILE__) + ' . $ttl . ' < time())return false;' . "\r\n";
    }

    /**
     * 获取缓存文件路径
     *
     * 根据配置获取指定$key所对应的缓存文件路径
     *
     * @author    Garbin
     * @access public
     * @param string    $key    缓存标识
     * @return string   缓存文件路径
     */
    protected function _getCachePath($key)
    {
        $cache_dir = $this->_config['path'];
        if ($this->_config['max_dir'])
        {
            $dir = str_pad(abs(crc32($key)) % $this->_config['max_dir'], 4, '0', STR_PAD_LEFT);
            $cache_dir .= $dir . '/';
            smkdir($cache_dir);
        }

        return $cache_dir . $this->_getFileName($key);
    }

    /**
     * 获取缓存文件名
     *
     * 获取指定$key对应的缓存文件名
     *
     * @author    Garbin
     * @access public
     * @param string $key 缓存标识
     * @return string 缓存文件名
     */
    protected function _getFileName($key)
    {
        return md5($key) . '.cache.php';
    }
}

/**
 * Memcached缓存服务器
 *
 * 提供memcache缓存服务
 *
 * @author    Garbin
 */
class MemcachedServer  implements ICacheServer
{
    /**
     * memcache实例
     */
    protected $_memcached = null;

    /**
     *    添加缓存服务器
     *
     *    添加缓存服务器至系统，可批量添加
     *
     *    @author    Garbin
     *    @param     array $servers 缓存服务器，格式与memcached::addservers参数一致
     *    @return    void
     */
    public function addServers($servers)
    {
        if ($this->_memcached === null)
        {
            $this->_memcached = new Memcached();
        }
        $this->_memcached->addServers($servers);
    }

    public function set($key, $value, $ttl = 0)
    {
        return $this->_memcached->set($key, $value, 0, $ttl);
    }

    public function get($key)
    {
        return $this->_memcached->get($key);
    }

    public function clear()
    {
        return $this->_memcached->flush();
    }

    public function delete($key)
    {
        return $this->_memcached->delete($key);
    }
}
